//*********************************************************
//
//    Copyright (c) Microsoft. All rights reserved.
//    This code is licensed under the Apache License, Version 2.0.
//    THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
//    ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
//    IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
//    PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
//
//*********************************************************

using System;
using System.Collections;
using System.Collections.Generic;

using System.Configuration.Install;
using ER = Microsoft.Research.DataLayer;
using Microsoft.Win32;
using System.Collections.ObjectModel;
using System.Reflection;
using System.IO;
namespace TridentHPCInstaller
{
    partial class TridentHPCInstaller
    {
        enum NodeSelection
        {
            HPCCluster = 0,
            HPCComputeNode = 1,
            Both = 2
        }
        //parameter strings from wxs        
        const string hpcNodeType = @"hpcNodeType";
        const string hpcUserName = @"hpcUserName";
        const string hpcPassword = @"hpcPassword";
        const string tridentComputePackPath = @"tridentComputePackPath";

        //Registry sub keys 
        private string clusterRegistryKey = @"Software\Microsoft\HPC\";
        private string clusterSubKey = @"ClusterName";

        //private variables
        private ER.Connection registryConn = null;
        private string hpcHeadNodeName = string.Empty;
        const int maxHeadNodeExecutorCount = 1;
        private static string libraryPath = string.Empty;
        private static string localPath = string.Empty;
        //error messages
        const string invalidHPCUserPass = @"HPC Server Username and Password cannot be empty";
        const string invalidComputePackPath = @"Trident compute pack installed path is empty";
        const string invalidClusterRegKey = @"Error reading ClusterName";
        const string invalidExectorCount = @"Multiple non-interactive executors found on head node";
        /// <summary>
        /// Required designer variable.
        /// </summary>
        private System.ComponentModel.IContainer components = null;

        /// <summary> 
        /// Clean up any resources being used.
        /// </summary>
        /// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
        protected override void Dispose(bool disposing)
        {
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }

        #region Component Designer generated code

        /// <summary>
        /// Required method for Designer support - do not modify
        /// the contents of this method with the code editor.
        /// </summary>
        private void InitializeComponent()
        {
            components = new System.ComponentModel.Container();
        }

        /// <summary>
        /// Install code gets invoked when, called with installutil 
        /// </summary>
        /// <param name="stateSaver"></param>
        public override void Install(System.Collections.IDictionary stateSaver)
        {
            ResolveEventHandler resolveHandler = new ResolveEventHandler(CurrentDomain_AssemblyResolve);
            try
            {
                base.Install(stateSaver);

                //read the hpc head node name from windows registry
                LoadHpcHeadName();

                AppDomain.CurrentDomain.AssemblyResolve += resolveHandler;

                //Open the registry connection
                OpenRegistryConnection();

                string nodeType = Context.Parameters[hpcNodeType].ToString();
                NodeSelection typeOfNode = (NodeSelection)Convert.ToInt32(nodeType);
                if (typeOfNode == NodeSelection.HPCCluster || typeOfNode == NodeSelection.Both)
                {
                    string userName = Context.Parameters[hpcUserName].ToString();
                    string password = Context.Parameters[hpcPassword].ToString();
                    if (string.IsNullOrEmpty(hpcUserName) || string.IsNullOrEmpty(hpcPassword))
                    {
                        throw new InstallException(invalidHPCUserPass);
                    }
                    //install HPC Head node
                    InstallHeadNode(userName, password);
                }
                //Install the compute path
                if (typeOfNode == NodeSelection.HPCComputeNode || typeOfNode == NodeSelection.Both)
                {
                    Assembly myAssembly = Assembly.GetExecutingAssembly();
                    string myPath = Path.GetDirectoryName(myAssembly.Location);
                    InstallComputeNode(myPath);
                }
            }
            catch (Exception exp)
            {
                Context.LogMessage(exp.Message);
                Context.LogMessage(exp.StackTrace);
                if (null != exp.InnerException)
                {
                    Context.LogMessage(exp.InnerException.Message);
                    Context.LogMessage(exp.InnerException.StackTrace);
                }
                throw exp;
            }
            finally
            {
                AppDomain.CurrentDomain.AssemblyResolve -= resolveHandler;
            }
        }


        static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
        {
            string asmFilePath = args.Name;
            string asmFile = Path.GetFileName(asmFilePath);

            try
            {
                //In case of assembly info create the dll name and try to load it from the current dir
                //if not try it with the AlternateProviderLocation which will be set from GetProviders()
                //If we receive an actual path split will return only 1 string
                string[] strAssembly = asmFilePath.Split(',');
                if (strAssembly.Length > 1 && !string.IsNullOrEmpty(strAssembly[0]))
                {
                    asmFile = string.Format("{0}.dll", strAssembly[0]);
                }
                Assembly[] asms;
                asms = AppDomain.CurrentDomain.GetAssemblies();
                foreach (Assembly asm in asms)
                {
                    string loadedAsm = Path.GetFileName(asm.Location);
                    if (string.Equals(loadedAsm, asmFile, StringComparison.InvariantCultureIgnoreCase))
                    {
                        return asm;
                    }
                }

                Assembly try1 = Assembly.LoadFile(Path.Combine(localPath, asmFile));
                return try1;
            }
            catch
            {
                if (!string.IsNullOrEmpty(libraryPath))
                {
                    string asmCandidate = Path.Combine(libraryPath, asmFile);
                    Assembly try2 = Assembly.LoadFile(asmCandidate);
                    return try2;
                }
            }
            return null;
        }


        /// <summary>
        /// Load the hpc head node name from the windows registry
        /// </summary>
        private void LoadHpcHeadName()
        {
            RegistryKey hpcClusterNameKey = Registry.LocalMachine.OpenSubKey(clusterRegistryKey, false);
            if (null != hpcClusterNameKey)
            {
                hpcHeadNodeName = hpcClusterNameKey.GetValue(clusterSubKey) as string;
            }
            if (string.IsNullOrEmpty(hpcHeadNodeName))
            {
                throw new InstallException(invalidClusterRegKey);
            }
        }

        /// <summary>
        ///Create registry connection for populating the node details to registry 
        /// </summary>
        private void OpenRegistryConnection()
        {
            Assembly me = Assembly.GetExecutingAssembly();
            localPath = Path.GetDirectoryName(me.Location);
            libraryPath = Path.Combine(localPath, "Libraries");

            //Put the alternate location
            ER.ConnectionManager.AlternateProviderLocation = localPath;
            //Initialize connection manager
            ER.SR_Connection.Init();
            ER.ConnectionManager cmgr = ER.ConnectionManager.Create(ER.ConnectionManager.CancelBehavior.ThrowException);
            registryConn = cmgr.PickConnection(ER.ConnectionManagerBase.ConnectionUI.NeverShowUI);
        }

        /// <summary>
        /// Instlal the head node machine object in registry with user name and password
        /// </summary>
        /// <param name="hpcUserName"></param>
        /// <param name="hpcPassword"></param>
        private void InstallHeadNode(string userName, string password)
        {
            ER.Machine headNode = ER.Machine.GetCreateForCluster(hpcHeadNodeName, registryConn);
            headNode.Parent = null;
            headNode.ClusterPassword = password;
            headNode.ClusterUser = userName;
            headNode.Save();
        }

        /// <summary>
        /// Install the compute node path on head node so that the HPC scheduler can use this path to launch the workflow launcher
        /// </summary>
        /// <param name="tridenComputePackPath"></param>
        private void InstallComputeNode(string tridenComputePackPath)
        {
            ER.Machine node = null;
            string computeNodeName = Environment.MachineName;

            //Loading the headnode outside try catch to make sure install fails if head node is not installed
            Collection<ER.Machine> headNode = ER.Machine.CommonSearches.GetMachineByName(registryConn, false, hpcHeadNodeName);
            if (headNode.Count != maxHeadNodeExecutorCount)
            {
                throw new InstallException(invalidExectorCount);
            }
            try
            {
                node = ER.Machine.Load(computeNodeName, registryConn);
                node.Parent = headNode[0];
                node.Availability = ER.ExecutorAvailabiltiy.Online;
                //Update the comutepack installed path on headnode
            }
            catch (ER.RecordNotFoundException)
            {
                node = ER.Machine.Create(computeNodeName, ER.MachineType.HPCComputeNode, registryConn);
                node.Parent = headNode[0];
                ER.HealthCheck.Create(node, registryConn);
                node.Availability = ER.ExecutorAvailabiltiy.Online;
            }
            node.Save();
            headNode[0].ComputeInstalledDirectory = tridenComputePackPath;
            headNode[0].Save();
        }
        #endregion
    }
}
